﻿// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>
#include <WinUser.h>
#include <stdio.h>
#include <ctime>
#include <string>
#include <thread>
#include <tlhelp32.h>
#include <vector>
#include <dbghelp.h>
#include <Shlwapi.h>

#pragma comment(lib, "shlwapi.lib")

void showcurrentstack();
void ShowThreadStack(DWORD);
void DumpStr(const char* str);
void GenerateDumpFilePath(std::string);
DWORD g_dwUIThreadID = 0;
FILE* g_stream = NULL;
WNDPROC g_pWindowOldProc = NULL;
HANDLE  g_hEvent = NULL;
HWND g_hwnd = NULL;
char g_dumpLogPath[MAX_PATH] = { 'D', ':', '\\', 'U', 'I','H', 'a', 'l', 't','.', 'l', 'o','g', 0, 0};
int g_iHaltNum = 1;
int g_iWaitHaltTime = 1000;

#define WM_NOTIFYMSG (WM_USER+0xF8)

///< 枚举窗口参数
typedef struct
{
    HWND hwndWindow; // 窗口句柄
    DWORD dwProcessID; // 进程ID
    DWORD dwMainTID;
}EnumWindowsArg;
///< 枚举窗口回调函数
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
    EnumWindowsArg* pArg = (EnumWindowsArg*)lParam;
    DWORD dwProcessID = 0;
    // 通过窗口句柄取得进程ID
    DWORD dwTid = ::GetWindowThreadProcessId(hwnd, &dwProcessID);
    if (dwProcessID == pArg->dwProcessID &&
        pArg->dwMainTID != 0 && pArg->dwMainTID == dwTid)
    {
        pArg->hwndWindow = hwnd;
        // 找到了返回FALSE
        return FALSE;
    }
    // 没找到，继续找，返回TRUE
    return TRUE;
}
///< 通过进程ID获取窗口句柄
HWND GetWindowHwndByPID(DWORD dwTid, DWORD dwProcessID)
{
    HWND hwndRet = NULL;
    EnumWindowsArg ewa;
    ewa.dwProcessID = dwProcessID;
    ewa.dwMainTID = dwTid;
    ewa.hwndWindow = NULL;
    EnumWindows(EnumWindowsProc, (LPARAM)&ewa);
    if (ewa.hwndWindow)
    {
        hwndRet = ewa.hwndWindow;
    }
    return hwndRet;
}

LRESULT CALLBACK CallWindProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
    if (WM_NOTIFYMSG == message)
    {
        SetEvent(g_hEvent);
    }

    if (g_pWindowOldProc)
        return (*g_pWindowOldProc)(hwnd, message, wparam, lparam);  //调用原来的窗口过程
    return DefWindowProc(hwnd, message, wparam, lparam);  //调用默认的窗口过程函数
}

void CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR uRealTimeID, DWORD dwTime)
{
    wchar_t szBuf[200] = { 0 };
    wsprintf(szBuf, L"TimerProc tid:%d hwnd:0x%x, msg:%d uRealTimeID:%d dwTime:%d \r", GetCurrentThreadId(), hwnd, uMsg, uRealTimeID, dwTime);
    OutputDebugString(szBuf);
}

std::string convertime(time_t curtime)
{
    struct tm* info;
    char buffer[80];
    info = localtime(&curtime);
    strftime(buffer, 80, "%Y-%m-%d %H:%M:%S ", info);
    return std::string(buffer);
}

std::string convertime2(time_t curtime)
{
    struct tm* info;
    char buffer[80];
    info = localtime(&curtime);
    strftime(buffer, 80, "%Y_%m_%d %H_%M_%S", info);
    return std::string(buffer);
}

static DWORD _stdcall HandleThread(void*)
{
    DWORD dwNum = 0;
    bool bHanged = false;
    DWORD dwStartTime = 0;
    time_t t_starttime = NULL;
    while (1)
    {
        ::PostMessage(g_hwnd, WM_NOTIFYMSG, 0, 0);
        if (WaitForSingleObject(g_hEvent, 20) == WAIT_TIMEOUT)
        {
            ::PostMessage(g_hwnd, WM_NOTIFYMSG, 0, 0);
            if (!bHanged)
            {
                dwStartTime = GetTickCount() - 20;
                time(&t_starttime);
            }
            if (WaitForSingleObject(g_hEvent, g_iWaitHaltTime) == WAIT_TIMEOUT)
            {
                wprintf(L"\r\n------------------ UI Hanged up (%d)---------------------\r\n", dwNum++);

                ::PostMessage(g_hwnd, WM_NOTIFYMSG, 0, 0);
                bHanged = true;

                char szBuf[200] = { 0 };
                time_t curtime = 0;
                time(&curtime);
                sprintf_s(szBuf, "--------NOW TIME:%s (START HANG TIME:%s)--(%d ms)-----------------\r\n", convertime(curtime).c_str(), convertime(t_starttime).c_str(), GetTickCount() - dwStartTime);
                DumpStr(szBuf);
                ShowThreadStack(g_dwUIThreadID);
                GenerateDumpFilePath(convertime2(curtime));
            }
            else  // wait ok   释放了
            {
                if (bHanged)
                {
                    bHanged = false;
                    ::PostMessage(g_hwnd, WM_NOTIFYMSG, 0, 0);
                    wprintf(L"\n >>>>>>>>>>>>>>>>>>UI Hanged up,time:%d >>>>>>>>>>>>>>>>>>\n", g_iHaltNum);

                    char szBuf[200] = { 0 };
                    time_t curtime = 0;
                    time(&curtime);
                    sprintf_s(szBuf, ">> (%d) >>>>>>>>>>>>>>>>>>  START:%s  >>>>>>>  END:%s >>>>>>>>> hang up total time:%d >>>>>>>>>>>> \r\r\r", g_iHaltNum++, convertime(t_starttime).c_str(), convertime(curtime).c_str(), GetTickCount() - dwStartTime);
                    DumpStr(szBuf);
                }
            }
        }
        else
        {
            if (bHanged)  // wait ok   释放了
            {
                bHanged = false;
                ::PostMessage(g_hwnd, WM_NOTIFYMSG, 0, 0);
                wprintf(L"\n >>>>>>>>>>>>>>>>>> Hanged up,time:%d >>>>>>>>>>>>>>>>>>\n", g_iHaltNum);

                char szBuf[200] = { 0 };
                time_t curtime = 0;
                time(&curtime);
                sprintf_s(szBuf, ">> (%d) >>>>>>>>>>>>>>>>>>  START:%s  >>>>>>>  END:%s >>>>>>>>> hang up total time:%d >>>>>>>>>>>> \r\r\r", g_iHaltNum++, convertime(t_starttime).c_str(), convertime(curtime).c_str(), GetTickCount() - dwStartTime);
                DumpStr(szBuf);
            }
            else
            {
                WaitForSingleObject(g_hEvent, 10);
            }
        }
    }

    return 0;
}

static VOID EnumerateThreads(std::vector<DWORD>& vtTid)
{
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    if (hSnapshot != INVALID_HANDLE_VALUE)
    {
        THREADENTRY32 te;
        te.dwSize = sizeof(THREADENTRY32);
        if (Thread32First(hSnapshot, &te))
        {
            do
            {
                if (te.dwSize >= (FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(DWORD))
                    && te.th32OwnerProcessID == GetCurrentProcessId()
                    && te.th32ThreadID != GetCurrentThreadId())
                {
                    vtTid.push_back(te.th32ThreadID);
                }

                te.dwSize = sizeof(THREADENTRY32);
            } while (Thread32Next(hSnapshot, &te));
        }
        CloseHandle(hSnapshot);
    }
}

void GenerateDumpLogPath()
{
    char szPath[MAX_PATH] = { 0 };
    ::GetModuleFileNameA(NULL, szPath, 255);
    std::string strPath = szPath;
    size_t pos = strPath.rfind("\\");
    if (pos != std::string::npos)
    {
        sprintf(g_dumpLogPath, "%s\\UIHaltDetector_%s_%d.log",
                const_cast<char*>(strPath.substr(0, pos).c_str()), 
            strPath.substr(pos+1, strPath.length() - pos - 5).c_str(), GetCurrentProcessId());
    }
}

void GenerateDumpFilePath(std::string strTime)
{
    static int iNum = 0;

    char szPath[MAX_PATH] = { 0 };
    ::GetModuleFileNameA(NULL, szPath, 255);
    std::string strPath = szPath;
    size_t pos = strPath.rfind("\\");
    if (pos != std::string::npos)
    {
        CreateDirectoryA(std::string(strPath.substr(0, pos) + std::string("\\UIHaltDmp\\")).c_str(), NULL);

        char dumpPath[MAX_PATH];
        sprintf(dumpPath, "%s\\UIHaltDmp\\UIHaltD_%s_%d[%s](%d).dmp",
            const_cast<char*>(strPath.substr(0, pos).c_str()),
            strPath.substr(pos + 1, strPath.length() - pos - 5).c_str(), GetCurrentProcessId(), strTime.c_str(), iNum++);

        HANDLE hFile = CreateFileA(dumpPath,
            GENERIC_WRITE,
            FILE_SHARE_READ,
            NULL,
            OPEN_ALWAYS,
            0,
            NULL);

        BOOL b = MiniDumpWriteDump(GetCurrentProcess(),
            GetCurrentProcessId(),
            hFile,
            MiniDumpNormal,
            NULL,
            NULL,
            NULL);
        
        CloseHandle(hFile);
    }
}

std::wstring GetCurrentExePath()
{
    wchar_t szPath[MAX_PATH] = { 0 };
    ::GetModuleFileName(0, szPath, _countof(szPath) - 1);
    std::wstring strPath = szPath;
    size_t pos = strPath.rfind(L'\\');
    if (pos != std::wstring::npos)
        strPath = strPath.substr(0, pos);

    return strPath;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        DisableThreadLibraryCalls(hModule);

        if (!g_pWindowOldProc)
        {
            AllocConsole();
            //freopen("CONOUT$", "w+t", stdout);
            freopen_s(&g_stream, "conout$", "w", stdout);

            g_hEvent = CreateEventW(NULL, FALSE, TRUE, L"wait_for_halt_event");

            wchar_t szProfile[30] = { 0 };
            wchar_t szIniPath[MAX_PATH] = { 0 };
            wsprintf(szIniPath, L"%s%s", GetCurrentExePath().c_str(), L"\\UIHaltDetector.ini");
            GetPrivateProfileStringW(L"UIHaltDetector", L"detectTime", L"1000", szProfile, 30, szIniPath);
            g_iWaitHaltTime = _wtoi(szProfile);
            g_iWaitHaltTime = (g_iWaitHaltTime < 100) ? 100 : g_iWaitHaltTime;

            GenerateDumpLogPath();

            std::vector<DWORD> vtTidList;
            EnumerateThreads(vtTidList);
            DWORD pid = 0;
            g_hwnd = GetWindowHwndByPID(vtTidList.size() > 0 ? vtTidList[0] : 0, GetCurrentProcessId());
            g_dwUIThreadID = GetWindowThreadProcessId(g_hwnd, &pid);
            g_pWindowOldProc = (WNDPROC)SetWindowLongPtrW((HWND)g_hwnd, /*GWL_WNDPROC*/-4, (LONG_PTR)CallWindProc);

            DWORD dwThreadId = 0;
            HANDLE hThread = (HANDLE)::CreateThread(NULL,
                0,
                &HandleThread,
                0,
                0,
                &dwThreadId);

            wchar_t szTitle[100] = { 0 };
            GetWindowText(g_hwnd, szTitle, 98);
            //UINT_PTR uRealTimeID = ::SetTimer(NULL, 10028, 1000, TimerProc);
            wchar_t szBuf[300] = { 0 };
            wsprintf(szBuf, L"PID:%d, Main TID:%d [UI HWND:0x%x  PID:%d TID:%d (%s)]\r", 
                GetCurrentProcessId(), vtTidList.size() > 0 ? vtTidList[0] : 0, g_hwnd, pid, g_dwUIThreadID, szTitle);
            //fprintf(g_stream, "%s", szBuf);
            wprintf(szBuf);
        }
        break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

